/* 
* Copyright (c) 2009, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using Bio.Data.Interfaces;
using Bio.Data.Providers.Virtualization;

namespace Bio.Data.Providers.Alignment.FastA
{
    /// <summary>
    /// This reads a FastA file sequence
    /// </summary>
    [DebuggerDisplay("{Id}:{Name}:{ScientificName}")]
    internal class VirtualizedFastASequence : BioExtensiblePropertyBase, IBioSequence, IVirtualizingListDataProvider<IBioSymbol>
    {
        private readonly FastAFile _container;
        private int _forcedCount, _dataCount;
        private long _blockLength;
        private List<Tuple<int, long>> _dataPositions;

        /// <summary>
        /// The unique identifier for this biological entity
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// The unique identifier or the course for this entity.
        /// </summary>
        public string SourceId { get { return string.Empty; } }

        /// <summary>
        /// The common name for this biological entity (may be same as SciName)
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Scientific name
        /// </summary>
        public string ScientificName { get; set; }

        /// <summary>
        /// Taxonomy identifier
        /// </summary>
        public string TaxonomyId { get; set; }

        /// <summary>
        /// This represents the validator used to validate the entity
        /// </summary>
        public IBioValidator Validator { get; private set; }

        /// <summary>
        /// The first column in the data stream with data
        /// </summary>
        public int FirstDataColumn { get; private set; }

        /// <summary>
        /// Unaligned sequence of data
        /// </summary>
        public IList<IBioSymbol> SequenceData { get; private set; }

        internal VirtualizedFastASequence(FastAFile owner, IBioValidator validator, int firstNonGap, IEnumerable<Tuple<int,long>> dataPositions, int nucelotideCount, long blockLength)
        {
            Validator = validator;
            FirstDataColumn = firstNonGap;
            SequenceData = new VirtualizingList<IBioSymbol>(this, Math.Min(1024, nucelotideCount), 60);
            _forcedCount = _dataCount = nucelotideCount;
            _blockLength = blockLength;

            _container = owner;
            _dataPositions = dataPositions.ToList();
        }

        /// <summary>
        /// This is used to force the stream to a specific size.
        /// </summary>
        /// <param name="count"></param>
        internal void ForceCount(int count)
        {
            if (count < _dataCount)
                throw new ArgumentOutOfRangeException("Count may not be less than real data size");
            _forcedCount = count;
        }

        /// <summary>
        /// Returns the total known count of items
        /// </summary>
        public int TotalCount { get { return _forcedCount; } }

        /// <summary>
        /// Loads a block of the data bounded by a starting index and count.
        /// </summary>
        /// <param name="startIndex"></param>
        /// <param name="count"></param>
        /// <returns>Enumerable range</returns>
        public IEnumerable<IBioSymbol> LoadRange(int startIndex, int count)
        {
            // Bound the read count
            if (startIndex + count > _forcedCount)
                count = _forcedCount - startIndex;

            // If they are requesting the end of the data, just return none.
            if (startIndex > _dataCount)
                return Enumerable.Repeat(BioSymbol.None, count);

            // Find the starting block using our indexes.
            long startMapPosition = 0;
            int skipCount = 0;
            for (int i = 0; i < _dataPositions.Count; i++)
            {
                if (_dataPositions[i].Item1 == startIndex)
                {
                    startMapPosition = _dataPositions[i].Item2;
                    break;
                }
                else if (_dataPositions[i].Item1 > startIndex)
                {
                    Debug.Assert(i > 0);
                    startMapPosition = _dataPositions[i - 1].Item2;
                    skipCount = startIndex - _dataPositions[i - 1].Item1;
                    break;
                }
            }

            // Find the ending block using the indexes.
            long lenToMap;
            var endBlock = _dataPositions.Where(dp => dp.Item1 > startIndex + count).FirstOrDefault();
            if (endBlock != null)
                lenToMap = endBlock.Item2 - startMapPosition;
            else
                lenToMap = _dataPositions[0].Item2 + _blockLength - startMapPosition;

            string line;
            using (var fs = _container.MmFile.CreateViewStream(startMapPosition, lenToMap, MemoryMappedFileAccess.Read))
            using (var ts = new StreamReader(fs))
            {
                line = String.Join("", ts.Lines());
            }

            // Return the sequence - skip the elements we don't want, grab all the loaded data and pad to the required count
            return line
                .Skip(skipCount)
                .Select(ch => FastAFile.CreateBioSymbol(Validator, ch))
                .Take(count)
                .PadRight(BioSymbol.None, count);
        }
    }
}